﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Odelle.Utils.Comparisons;

namespace Odelle.Utils.Comparisons
{
    public class StringTester : StringComparator
    {
        /***
         [Parameter]
            Format1;Format2;Format3;...   
            
         [Sample]
            allowblank;form:xxxx-xxxx-xxxx-xxxx;OR string:4~4

         [Formats]
            AND prefix (default):
                AND operation with previous result

            OR prefix:
                OR operation with previous result

            allowblank:
                accept null-string       
         
            integer:	integer number (with range of acceptable values)
                int, int:nnn~mmm, int:nnn~, int:~mmm
                    aka int or int64, like int16 or int32

            number:		double precision real number (with range of acceptable values)
                number, number:nnn~mmm, number:nnn~, number:~mmm
                    aka double

            form:		formed string like 010-123-3467
                0 - decimal number
                8 - octal number
                X - hexa-decimal (with upper cases)
                x - hexa-decimal (with lower cases)
		
                A - upper case
                a - lower case
                L - alphabet letters
                % - alphabet or number

                * - any
                else - the character

            text:		string (with range of acceptable lengths)
                text, text:mmm, text:nnn~mmm

         ***/

        public override bool Compare(string value, string formatValue)
        {
            string[] formats;
            bool matched = false;

            if (formatValue == "")
            {
                formats = new string[1];
                formats[0] = formatValue;
            }
            else if (formatValue.Contains(';'))
            {
                formats = formatValue.Split(';');
            }
            else
            {
                formats = new string[1];
                formats[0] = formatValue;
            }

            for (int i = 0; i < formats.Length; ++i)
            {
                if (value == "" && formats[i].ToLower() == "allowblank")
                {
                    matched = true;
                    break;
                }

                bool operateAnd = true;

                if (formats[i].StartsWith("OR ") || formats[i].StartsWith("or "))
                {
                    operateAnd = false;
                    formats[i] = formats[i].Substring(3);
                }
                else if (formats[i].StartsWith("AND ") || formats[i].StartsWith("and "))
                {
                    operateAnd = true;
                    formats[i] = formats[i].Substring(4);
                }

                if (operateAnd)
                {
                    matched = matched && TestOnce(value, formats[i]);
                }
                else
                {
                    matched = matched || TestOnce(value, formats[i]);
                }
            }

            return matched;
        }

        public bool TestOnce(string value, string format)
        {
            bool matched = true;

            string arg1 = "";
            string arg2 = "";

            if (format == "")
            {
            }
            else if (format.Contains(':'))
            {
                arg1 = format.Substring(0, arg1.IndexOf(':'));
                arg2 = format.Substring(arg1.IndexOf(':'));
            }
            else
            {
                arg1 = format;
            }

            switch (arg1.ToLower())
            {
                case "":
                    matched = (value == format);
                    break;

                case "int":
                case "integer":
                case "int64":
                    Int64 valueInt64 = 0;
                    matched = Int64.TryParse(value, out valueInt64);
                    matched = matched && CheckRange(valueInt64, arg2);
                    break;
                case "int16":
                    Int16 valueInt16 = 0;
                    matched = Int16.TryParse(value, out valueInt16);
                    matched = matched && CheckRange((Int64)valueInt16, arg2);
                    break;
                case "int32":
                    Int32 valueInt32 = 0;
                    matched = Int32.TryParse(value, out valueInt32);
                    matched = matched && CheckRange((Int64)valueInt32, arg2);
                    break;

                case "number":
                case "double":
                    double valueDouble = 0.0;
                    matched = Int64.TryParse(value, out valueInt64);
                    matched = matched && CheckRange(valueDouble, arg2);
                    break;

                case "form":
                    matched = CheckForm(value, arg2);
                    break;

                case "text":
                    matched = CheckLength(value, arg2);
                    break;

                default:
                    matched = (value == format);
                    break;
            }

            return matched;
        }

        #region Checkers

        private bool CheckForm(string value, string arg2)
        {
            if (value.Length != arg2.Length)
                return false;

            bool result = true;

            for (int i = 0; i < value.Length; ++i)
            {
                char target = value.ToCharArray()[i];
                char arg = arg2.ToCharArray()[i];

                switch (arg)
                {
                    case '0':
                        result = result && char.IsNumber(target);
                        break;
                    case 'A':
                        result = result && char.IsUpper(target);
                        break;
                    case 'a':
                        result = result && char.IsNumber(target);
                        break;

                    case '2':
                        result = result && "01".Contains(target);
                        break;
                    case '8':
                        result = result && "01234567".Contains(target);
                        break;
                    case 'X':
                        result = result && "0123456789ABCDEF".Contains(target);
                        break;
                    case 'x':
                        result = result && "0123456789abcdef".Contains(target);
                        break;

                    case 'L':
                        result = result && char.IsLetter(target);
                        break;
                    case '%':
                        result = result && (char.IsLetter(target) || char.IsNumber(target));
                        break;
                    case '*':
                        result = result && true;
                        break;

                    default:
                        result = result && (target == arg);
                        break;
                }

                if (!result)
                    break;
            }

            return result;
        }

        private bool CheckRange(Int64 value, string arg2)
        {
            if (arg2 == "")
                return true;

            if (arg2.Contains('~'))
            {
                string[] args = arg2.Split('~');
                if (args.Length != 2)
                {
                    Int64 argMin, argMax;

                    bool isMin, isMax;

                    isMin=Int64.TryParse(args[0], out argMin);
                    isMax=Int64.TryParse(args[1], out argMax);

                    if (isMin && isMax)
                    {
                        if (argMin <= value && value <= argMax)
                            return true;
                    }
                    else if (isMin)
                    {
                        if (argMin <= value )
                            return true;
                    }
                    else if (isMax)
                    {
                        if (value <= argMax)
                            return true;
                    }
                }
            }
            else
            {
                Int64 arg;
                if (Int64.TryParse(arg2, out arg))
                {
                    if (arg >= 0)
                    {
                        if (0 <= value && value <=arg)
                            return true;
                    }
                    else
                    {
                        if (arg <= value && value <= 0)
                            return true;
                    }
                }
            }

            return false;
        }

        private bool CheckRange(double value, string arg2)
        {
            if (arg2 == "")
                return true;

            if (arg2.Contains('~'))
            {
                string[] args = arg2.Split('~');
                if (args.Length != 2)
                {
                    double argMin, argMax;

                    bool isMin, isMax;

                    isMin = double.TryParse(args[0], out argMin);
                    isMax = double.TryParse(args[1], out argMax);

                    if (isMin && isMax)
                    {
                        if (argMin <= value && value <= argMax)
                            return true;
                    }
                    else if (isMin)
                    {
                        if (argMin <= value)
                            return true;
                    }
                    else if (isMax)
                    {
                        if (value <= argMax)
                            return true;
                    }
                }
            }
            else
            {
                double arg;
                if (double.TryParse(arg2, out arg))
                {
                    if (arg >= 0)
                    {
                        if (0 <= value && value <= arg)
                            return true;
                    }
                    else
                    {
                        if (arg <= value && value <= 0)
                            return true;
                    }
                }
            }

            return false;
        }

        private bool CheckLength(string value, string arg2)
        {
            if (arg2 == "")
                return true;

            arg2=arg2.Replace('-','~');

            if (arg2.Contains('~'))
            {
                string[] args = arg2.Split('~');
                if (args.Length != 2)
                {
                    int argMin, argMax;

                    bool isMin, isMax;

                    isMin = int.TryParse(args[0], out argMin);
                    isMax = int.TryParse(args[1], out argMax);

                    if (isMin && isMax)
                    {
                        if (argMin <= value.Length && value.Length <= argMax)
                            return true;
                    }
                    else if (isMin)
                    {
                        if (argMin <= value.Length)
                            return true;
                    }
                    else if (isMax)
                    {
                        if (value.Length <= argMax)
                            return true;
                    }
                }
            }
            return false;
        }

        #endregion
    }
}
